/* Copyright (c) 2004-2024 Peigen.info. All rights reserved. */
package info.peigen.hotpot.component.doc.core.resolver;

import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.TypeUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import info.peigen.hotpot.common.core.annotation.Comment;
import info.peigen.hotpot.common.core.enums.CommentType;
import info.peigen.hotpot.common.core.enums.YesOrNo;
import info.peigen.hotpot.component.doc.core.domain.HotpotApi;
import info.peigen.hotpot.component.doc.core.domain.HotpotDocAttribute;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.extern.slf4j.Slf4j;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * <b>(HotpotDocAttributeResolver)</b>
 *
 * @author Peigen
 * @version 1.0.0
 * @since 2023/12/8
 */
@Slf4j
public class HotpotDocAttributeResolver {
    final static List<Class<? extends Annotation>> REQUIRED_ANNOTATIONS = Lists.newArrayList(NotEmpty.class, NotNull.class);

    public static void resolverInAttribute(HotpotApi domain, Class<?> cls) {
        List<HotpotDocAttribute> inAttributes = resolver(cls);
        domain.setInAttributes(inAttributes);
    }

    public static void resolverOutAttribute(HotpotApi domain, Class<?> cls) {
        List<HotpotDocAttribute> outAttributes = resolver(cls);
        domain.setOutAttributes(outAttributes);
    }

    public static List<HotpotDocAttribute> resolver(Class<?> cls) {
        Field[] fields = cls.getDeclaredFields();
        return Arrays.stream(fields).map(field -> {
            HotpotDocAttribute att = HotpotDocAttribute.builder().name(field.getName()).size(resolverFieldSize(field))
                    .requiredFields(resolverRequiredField(field)).description(resolverDescription(field)).build();
            log.info("Class={},field={}", cls.getSimpleName(), field.getName());
            Comment comment = field.getAnnotation(Comment.class);
            if (ObjectUtil.isNotNull(comment) && comment.ignore()) {
                return null;
            }

            // 特殊描述处理
            CommentType commentType = CommentType.getByField(field);
            att.setType(commentType);

            switch (commentType) {
                case Enum -> att.setDescription(resolverEnumDescription(att.getDescription(), field.getType()));
                case Class -> {
                    att.setDescription(field.getType().getSimpleName());
                    att.setDescriptionClass(field.getType());
                }
                case List -> {
                    Type     type                   = TypeUtil.getActualType(field.getType(), field);
                    Class<?> parameterizedTypeClass = TypeUtil.getClass(TypeUtil.getTypeArgument(type));
                    if (parameterizedTypeClass.isEnum()) {
                        att.setDescription(resolverEnumDescription(att.getDescription(), parameterizedTypeClass));
                        att.setDescriptionClass(Enum.class);
                    } else if (StrUtil.equals(parameterizedTypeClass.getSimpleName(), "String")) {
                        att.setDescription(att.getDescription());
                        att.setDescriptionClass(String.class);
                    } else {
                        att.setDescription(att.getDescription() + ".\n见附录:" + parameterizedTypeClass.getSimpleName());
                        att.setDescriptionClass(parameterizedTypeClass);
                    }
                }
                case Json -> {
                    if (ObjectUtil.isNotNull(comment)) {
                        comment = field.getAnnotation(Comment.class);
                        Class<?> jsonClass = comment.jsonClass();
                        att.setDescription(att.getDescription() + ".\n见附录:" + jsonClass.getSimpleName());
                        att.setDescriptionClass(jsonClass);
                    }
                }
            }
            return att;
        }).toList();
    }

    private static YesOrNo resolverRequiredField(Field field) {
        if (REQUIRED_ANNOTATIONS.stream().anyMatch(anno -> AnnotationUtil.hasAnnotation(field, anno))) {
            return YesOrNo.Y;
        }
        return YesOrNo.N;
    }

    private static String resolverFieldSize(Field field) {
        Size size = AnnotationUtil.getAnnotation(field, Size.class);
        return ObjectUtil.isNotNull(size) ? String.valueOf(size.max()) : "";
    }

    private static String resolverDescription(Field field) {
        Comment comment = AnnotationUtil.getAnnotation(field, Comment.class);
        if (ObjectUtil.isNotNull(comment)) {
            return comment.value();
        }
        return null;
    }

    private static String resolverEnumDescription(Object description, Class<?> cls) {
        if (Arrays.stream(cls.getGenericInterfaces())
                .noneMatch(in -> StrUtil.contains(in.getTypeName(), "Messageable"))) {
            return "";
        }
        try {
            Object[]            objects    = cls.getEnumConstants();
            Method              getCode    = cls.getMethod("getCode");
            Method              getMessage = cls.getMethod("getMessage");
            Map<String, String> map        = Maps.newHashMap();
            List<Object> newObjects = Arrays.stream(objects)
                    .filter(o -> !StrUtil.equals(ReflectUtil.invoke(o, "getCode"), "--")).toList();
            for (Object obj : newObjects) {
                map.put(ReflectUtil.invoke(obj, getCode), ReflectUtil.invoke(obj, getMessage));
            }
            Map<Object, Map<String, String>> newMap = Maps.newHashMap();
            newMap.put(description, map);
            return JSON.toJSONString(newMap, JSONWriter.Feature.WriteEnumUsingToString);
        } catch (NoSuchMethodException ignored) {

        }
        return null;
    }

}
